/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.update.processor;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.DeleteUpdateCommand;
import org.apache.solr.update.UpdateCommand;
import org.junit.BeforeClass;

/** Tests various configurations of DocExpirationUpdateProcessorFactory */
public class DocExpirationUpdateProcessorFactoryTest extends UpdateProcessorTestBase {

  public static final String CONFIG_XML = "solrconfig-doc-expire-update-processor.xml";
  public static final String SCHEMA_XML = "schema15.xml";

  @BeforeClass
  public static void beforeClass() throws Exception {
    initCore(CONFIG_XML, SCHEMA_XML);
  }

  public void testTTLDefaultsConversion() throws Exception {
    SolrInputDocument d = null;

    d =
        processAdd(
            "convert-ttl-defaults",
            params("NOW", "1394059630042"),
            doc(f("id", "1111"), f("_ttl_", "+5MINUTES")));
    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            "convert-ttl-defaults",
            params(
                "NOW", "1394059630042",
                "_ttl_", "+5MINUTES"),
            doc(f("id", "1111")));

    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());
  }

  public void testTTLFieldConversion() throws Exception {
    final String chain = "convert-ttl-field";
    SolrInputDocument d = null;
    d =
        processAdd(
            chain,
            params("NOW", "1394059630042"),
            doc(f("id", "1111"), f("_ttl_field_", "+5MINUTES")));
    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            chain,
            params("NOW", "1394059630042"),
            doc(f("id", "2222"), f("_ttl_field_", "+27MINUTES")));
    assertNotNull(d);
    assertEquals(
        new Date(1394061250042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            chain,
            params("NOW", "1394059630042"),
            doc(f("id", "3333"), f("_ttl_field_", "+1YEAR")));
    assertNotNull(d);
    assertEquals(
        new Date(1425595630042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            chain,
            params("NOW", "1394059630042"),
            doc(f("id", "1111"), f("_ttl_field_", "/DAY+1YEAR")));
    assertNotNull(d);
    assertEquals(
        new Date(1425513600000L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    // default ttlParamName is disabled, this should not convert...
    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_", "+5MINUTES"),
            doc(f("id", "1111")));
    assertNotNull(d);
    assertNull(d.getFieldValue("_expire_at_tdt"));
  }

  public void testTTLParamConversion() throws Exception {
    final String chain = "convert-ttl-param";
    SolrInputDocument d = null;
    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_param_", "+5MINUTES"),
            doc(f("id", "1111")));

    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_param_", "+27MINUTES"),
            doc(f("id", "2222")));
    assertNotNull(d);
    assertEquals(
        new Date(1394061250042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    // default ttlFieldName is disabled, param should be used...
    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_param_", "+5MINUTES"),
            doc(f("id", "1111"), f("_ttl_field_", "+999MINUTES")));
    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    // default ttlFieldName is disabled, this should not convert...
    d =
        processAdd(
            chain, params("NOW", "1394059630042"), doc(f("id", "1111"), f("_ttl_", "/DAY+1YEAR")));
    assertNotNull(d);
    assertNull(d.getFieldValue("_expire_at_tdt"));
  }

  public void testTTLFieldConversionWithDefaultParam() throws Exception {
    final String chain = "convert-ttl-field-with-param-default";
    SolrInputDocument d = null;
    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_param_", "+999MINUTES"),
            doc(f("id", "1111"), f("_ttl_field_", "+5MINUTES")));
    assertNotNull(d);
    assertEquals(
        new Date(1394059930042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());

    d =
        processAdd(
            chain,
            params(
                "NOW", "1394059630042",
                "_ttl_param_", "+27MINUTES"),
            doc(f("id", "2222")));
    assertNotNull(d);
    assertEquals(
        new Date(1394061250042L).toInstant(),
        ((Date) d.getFieldValue("_expire_at_tdt")).toInstant());
  }

  public void testAutomaticDeletes() throws Exception {

    // get a handle on our recorder

    UpdateRequestProcessorChain chain = h.getCore().getUpdateProcessingChain("scheduled-delete");

    assertNotNull(chain);

    List<UpdateRequestProcessorFactory> factories = chain.getProcessors();
    assertEquals("did number of processors configured in chain get changed?", 5, factories.size());
    assertTrue(
        "Expected [1] RecordingUpdateProcessorFactory: " + factories.get(1).getClass(),
        factories.get(1) instanceof RecordingUpdateProcessorFactory);
    RecordingUpdateProcessorFactory recorder = (RecordingUpdateProcessorFactory) factories.get(1);

    // now start recording, and monitor for the expected commands

    try {
      recorder.startRecording();

      final int timeout = 30; // in seconds

      // more than one iter to verify it's recurring
      final int numItersToCheck = 1 + RANDOM_MULTIPLIER;

      for (int i = 0; i < numItersToCheck; i++) {
        UpdateCommand tmp;

        // be generous in how long we wait, some jenkins machines are slooooow
        tmp = recorder.commandQueue.poll(timeout, TimeUnit.SECONDS);
        assertNotNull("Unable to get UpdateCommand from queue within " + timeout + " seconds", tmp);

        // we can be confident in the order because DocExpirationUpdateProcessorFactory
        // uses the same request for both delete & commit operations -- and both
        // RecordingUpdateProcessorFactory's getInstance & startRecording methods are
        // synchronized.  So it should not be possible to start recording in the
        // middle of the two commands
        assertTrue(
            "expected DeleteUpdateCommand: " + tmp.getClass(), tmp instanceof DeleteUpdateCommand);

        DeleteUpdateCommand delete = (DeleteUpdateCommand) tmp;
        assertFalse(delete.isDeleteById());
        assertNotNull(delete.getQuery());
        assertTrue(
            delete.getQuery(), delete.getQuery().startsWith("{!cache=false}eXpField_tdt:[* TO "));

        // be generous in how long we wait, some jenkins machines are slooooow
        tmp = recorder.commandQueue.poll(timeout, TimeUnit.SECONDS);
        assertNotNull("Unable to get UpdateCommand from queue within " + timeout + " seconds", tmp);

        // commit should be immediately after the delete operation
        assertTrue(
            "expected CommitUpdateCommand: " + tmp.getClass(), tmp instanceof CommitUpdateCommand);

        CommitUpdateCommand commit = (CommitUpdateCommand) tmp;
        assertTrue(commit.softCommit);
        assertTrue(commit.openSearcher);
      }
    } finally {
      recorder.stopRecording();
    }
  }
}
